 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  * James D Miles (IBM Corp.) - bug 181375, ArrayIndexOutOfBoundsException in SiteSearchCategory$Query
  * James D Miles (IBM Corp.) - bug 191783, NullPointerException in FeatureDownloader
  *******************************************************************************/
 package org.eclipse.update.internal.search;


 import java.util.ArrayList ;
 import java.util.Arrays ;
 import java.util.HashMap ;
 import java.util.HashSet ;
 import java.util.List ;
 import java.util.Map ;

 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.update.core.ICategory;
 import org.eclipse.update.core.IFeature;
 import org.eclipse.update.core.ISite;
 import org.eclipse.update.core.ISiteFeatureReference;
 import org.eclipse.update.internal.core.ExtendedSite;
 import org.eclipse.update.internal.core.LiteFeature;
 import org.eclipse.update.internal.core.UpdateCore;
 import org.eclipse.update.search.IQueryUpdateSiteAdapter;
 import org.eclipse.update.search.IUpdateSearchFilter;
 import org.eclipse.update.search.IUpdateSearchQuery;
 import org.eclipse.update.search.IUpdateSearchResultCollector;

 /**
  * Searches an update site
  */
 public class SiteSearchCategory extends BaseSearchCategory {
     private IUpdateSearchQuery[] queries;
     private boolean liteFeaturesAreOK;
     private static final String CATEGORY_ID =
         "org.eclipse.update.core.unified-search"; //$NON-NLS-1$

     private static class Query implements IUpdateSearchQuery {
         
         private boolean liteFeaturesAreOK;
         
         
         public Query() {
             liteFeaturesAreOK = false;
         }
         
         public Query(boolean liteFeaturesAreOK) {
             this.liteFeaturesAreOK = liteFeaturesAreOK;
         }
         
         public boolean isLiteFeaturesAreOK() {
             return liteFeaturesAreOK;
         }

         public void setLiteFeaturesAreOK(boolean liteFeaturesAreOK) {
             this.liteFeaturesAreOK = liteFeaturesAreOK;
         }
         
         public void run(
             ISite site,
             String [] categoriesToSkip,
             IUpdateSearchFilter filter,
             IUpdateSearchResultCollector collector,
             IProgressMonitor monitor) {
             
             ISiteFeatureReference[] refs = site.getFeatureReferences();
             HashSet ignores = new HashSet ();
             Map liteFeatures = new HashMap ();
             
             if (categoriesToSkip != null) {
                 for (int i = 0; i < categoriesToSkip.length; i++) {
                     ignores.add(categoriesToSkip[i]);
                 }
             }
             List siteFeatureReferences = new ArrayList (Arrays.asList(refs));
             
             if (liteFeaturesAreOK && (site instanceof ExtendedSite) ) {
                 
                 ExtendedSite extendedSite = (ExtendedSite)site;
                 LiteFeature[] liteFeaturesArray = extendedSite.getLiteFeatures();
                 if ( (liteFeaturesArray != null) && ( liteFeaturesArray.length != 0)) {
                     for(int i = 0; i < liteFeaturesArray.length; i++) {
                         liteFeatures.put(liteFeaturesArray[i].getVersionedIdentifier(), liteFeaturesArray[i]);
                     }
                     (new FeatureDownloader(siteFeatureReferences, collector, filter, ignores, monitor, true, liteFeatures)).run();
                     return;
                 } else {
                     liteFeaturesAreOK = false;
                 }
             }
             

             
             monitor.beginTask("", refs.length); //$NON-NLS-1$
 ThreadGroup featureDownloaders = new ThreadGroup ("FeatureDownloader"); //$NON-NLS-1$
 int numberOfThreads = (refs.length > 5)? 5: refs.length;

             Thread [] featureDownloader = new Thread [numberOfThreads];
             for( int i = 0; i < numberOfThreads; i++) {
                 featureDownloader[i] = new Thread (featureDownloaders, new FeatureDownloader(siteFeatureReferences, collector, filter, ignores, monitor));
                 featureDownloader[i].start();
             }
             
             int i =0;
             while(i < numberOfThreads){
                 if (monitor.isCanceled()) {
                     synchronized(siteFeatureReferences) {
                         siteFeatureReferences.clear();
                     }
                 }
                 try {
                     featureDownloader[i].join(250);
                     if(!featureDownloader[i].isAlive()){
                         i++;
                     }
                 } catch (InterruptedException ie) {
                 }
             }
         }

         /* (non-Javadoc)
          * @see org.eclipse.update.internal.ui.search.ISearchQuery#getSearchSite()
          */
         public IQueryUpdateSiteAdapter getQuerySearchSite() {
             return null;
         }
     }

     public SiteSearchCategory() {
         super(CATEGORY_ID);
         queries = new IUpdateSearchQuery[] { new Query()};
     }

     public SiteSearchCategory(boolean liteFeaturesAreOK) {
         this();
         this.liteFeaturesAreOK = liteFeaturesAreOK;
         queries = new IUpdateSearchQuery[] { new Query(liteFeaturesAreOK)};
     }

     public IUpdateSearchQuery[] getQueries() {
         return queries;
     }
     
     
     private static class FeatureDownloader implements Runnable {
         
         private List siteFeatureReferences;
         
         private IProgressMonitor monitor;
         
         private IUpdateSearchFilter filter;
         
         private IUpdateSearchResultCollector collector;
         
         private HashSet ignores;

         private boolean liteFeaturesAreOK;

         private Map liteFeatures;

         public FeatureDownloader(List siteFeatureReferences, IUpdateSearchResultCollector collector, IUpdateSearchFilter filter, HashSet ignores, IProgressMonitor monitor) {
             super();

             this.collector = collector;
             this.filter = filter;
             this.ignores = ignores;
             this.monitor = monitor;
             this.siteFeatureReferences = siteFeatureReferences;
         }
         
         public FeatureDownloader(List siteFeatureReferences, IUpdateSearchResultCollector collector, IUpdateSearchFilter filter, HashSet ignores, IProgressMonitor monitor, boolean liteFeaturesAreOK, Map liteFeatures) {
             this(siteFeatureReferences, collector, filter, ignores, monitor);
             this.liteFeaturesAreOK = liteFeaturesAreOK && (liteFeatures != null);
             this.liteFeatures = liteFeatures;
         }

         public void run() {
             
             ISiteFeatureReference siteFeatureReference = null;
             
             while (siteFeatureReferences.size() != 0) {
                 
                 synchronized(siteFeatureReferences) {
                     try{
                         siteFeatureReference = (ISiteFeatureReference)siteFeatureReferences.remove(0);
                     }catch(IndexOutOfBoundsException e){
                         siteFeatureReference = null;
                         break;
                     }
                 }
                 if (siteFeatureReference != null) {
                     boolean skipFeature = false;
                     if (monitor.isCanceled())
                         break;
                     if (ignores.size() > 0) {
                         ICategory[] categories = siteFeatureReference.getCategories();
                         
                         for (int j = 0; j < categories.length; j++) {
                             ICategory category = categories[j];
                             if (ignores.contains(category.getName())) {
                                 skipFeature = true;
                                 break;
                             }
                         }
                     }
                     try {
                         if (!skipFeature) {
                             if (filter.accept(siteFeatureReference)) {
                                 IFeature feature = null;
                                 if(liteFeaturesAreOK) {
                                     feature = (IFeature)liteFeatures.get(siteFeatureReference.getVersionedIdentifier());
                                 }
                                 if (feature == null){
                                     feature = siteFeatureReference.getFeature(null);
                                 }
                                 synchronized(siteFeatureReferences) {
                                     if ( (feature != null) && (filter.accept(siteFeatureReference)) ) {
                                         collector.accept(feature);
                                         monitor.subTask(feature.getLabel());
                                     }
                                 }
                             }
                         }
                     } catch (CoreException e) {
                         UpdateCore.log(e);
                     } finally {
                         monitor.worked(1);
                     }
                 }
             }
             
         }
     }

     public boolean isLiteFeaturesAreOK() {
         return liteFeaturesAreOK;
     }

     public void setLiteFeaturesAreOK(boolean liteFeaturesAreOK) {
         this.liteFeaturesAreOK = liteFeaturesAreOK;
         for( int i = 0; i < queries.length; i++) {
             ((Query)queries[i]).setLiteFeaturesAreOK(liteFeaturesAreOK);
         }
     }
 }

